/* Copyright Thanh-Trung Pham, JAIST, 2012 */

package jp.ac.jaist.model;

import java.util.ArrayList;
import java.util.List;
import org.apache.commons.math.linear.Array2DRowRealMatrix;
import org.apache.commons.math.linear.LUDecompositionImpl;
import org.apache.commons.math.linear.MatrixUtils;
import org.apache.commons.math.linear.RealMatrix;

/**
 *
 * @author Thanh-Trung Pham
 */
public class RMExecutionModel {

    private List<RMExecutionState> m_oStates;
    private List<RMExecutionTransition> m_oTransitions;
    private int m_iCurrentStateID;
    private int m_iCurrrentTransitionID;
    private double[][] m_oMarkovChain;
    private double[][] m_oBMatrix;

    /**
     * The constructor.
     */
    public RMExecutionModel() {
        m_oStates = new ArrayList<RMExecutionState>();
        m_oTransitions = new ArrayList<RMExecutionTransition>();

        m_iCurrentStateID = -1;
        m_iCurrrentTransitionID = -1;

        RMExecutionState oSTARTState = new RMExecutionState();
        oSTARTState.setID(++m_iCurrentStateID);
        m_oStates.add(oSTARTState);
    }

    /**
     * Get the START state.
     * @return the START state.
     */
    public RMExecutionState getSTARTState() {
        return m_oStates.get(0);
    }

    /**
     * Add a state.
     * @param state a state.
     */
    public void addState(RMExecutionState state) {
        state.setID(++m_iCurrentStateID);
        m_oStates.add(state);
    }

    /**
     * Add a transition.
     * @param transition a transition.
     */
    public void addTransition(RMExecutionTransition transition) {
        transition.setID(++m_iCurrrentTransitionID);
        m_oTransitions.add(transition);
    }

    /**
     * Get the state collection.
     *
     * @return the state collection.
     */
    public RMExecutionState[] getStates() {
        return (RMExecutionState[]) m_oStates.toArray(new RMExecutionState[m_oStates.size()]);
    }

    /**
     * Get all the transitions.
     *
     * @return all the transitions.
     */
    public RMExecutionTransition[] getTransitions() {
        return (RMExecutionTransition[]) m_oTransitions.toArray(new RMExecutionTransition[m_oTransitions.size()]);
    }

    /**
     * Generate the Markov chain.
     */
    private void generateMarkovChain() {
        int iNumberOfStates = m_oStates.size();
        m_oMarkovChain = new double[iNumberOfStates * 2 + 1][iNumberOfStates * 2 + 1];

        for (int i = 0; i < iNumberOfStates; i++) {
            RMExecutionState oState = m_oStates.get(i);

            // The transition from [Correct input, a(k)] to FAILURE has probability sfp(a(k)).
            m_oMarkovChain[i * 2][iNumberOfStates * 2] = oState.getSfp();
            // The transition from [Erroneous input, a(k)] to FAILURE has probability sep(a(k)).
            m_oMarkovChain[i * 2 + 1][iNumberOfStates * 2] = oState.getSep();
        }

        for (int i = 0; i < m_oTransitions.size(); i++) {
            RMExecutionTransition oTransition = m_oTransitions.get(i);

            RMExecutionState oSourceState = oTransition.getSourceState();
            RMExecutionState oDestinationState = oTransition.getDestinationState();

            double dTransitionProbability = oTransition.getTransitionProbability();

            int iSourceStateID = oSourceState.getID();
            int iTargetStateID = oDestinationState.getID();

            // The transition from [Correct input, a(i)] to [Erroneous input, a(j)] has probability p(i,j)pfp(a(i)).
            m_oMarkovChain[iSourceStateID * 2][iTargetStateID * 2 + 1] = dTransitionProbability * oSourceState.getPfp();
            // The transition from [Correct input, a(i)] to [Correct input, a(j)] has probability p(i,j)[1-sfp(a(i))-pfp(a(i))].
            m_oMarkovChain[iSourceStateID * 2][iTargetStateID * 2] = dTransitionProbability * (1.0 - oSourceState.getSfp() - oSourceState.getPfp());
            // The transition from [Erroneous input, a(i)] to [Erroneous input, a(j)] has probability p(i,j)nmep(a(i)).
            m_oMarkovChain[iSourceStateID * 2 + 1][iTargetStateID * 2 + 1] = dTransitionProbability * oSourceState.getNmep();
            // The transition from [Erroneous input, a(i)] to [Correct input, a(j)] has probability p(i,j)[1-sep(a(i))-nmep(a(i))].
            m_oMarkovChain[iSourceStateID * 2 + 1][iTargetStateID * 2] = dTransitionProbability * (1.0 - oSourceState.getSep() - oSourceState.getNmep());
        }

        // With [Correct input, END], [Erroneous input, END] and FAILURE, add three self transitions with probabilities 1.0.
        m_oMarkovChain[(iNumberOfStates - 1) * 2][(iNumberOfStates - 1) * 2] = 1.0;
        m_oMarkovChain[(iNumberOfStates - 1) * 2 + 1][(iNumberOfStates - 1) * 2 + 1] = 1.0;
        m_oMarkovChain[iNumberOfStates * 2][iNumberOfStates * 2] = 1.0;
    }

    /**
     * Solve the Markov chain.
     */
    private void solveMarkovChain() {
        RealMatrix oMarkovChain = new Array2DRowRealMatrix(m_oMarkovChain);

        RealMatrix oQMatrix = oMarkovChain.getSubMatrix(0, oMarkovChain.getRowDimension() - 4, 0, oMarkovChain.getColumnDimension() - 4);
        RealMatrix oRMatrix = oMarkovChain.getSubMatrix(0, oMarkovChain.getRowDimension() - 4, oMarkovChain.getColumnDimension() - 3, oMarkovChain.getColumnDimension() - 1);

        RealMatrix oIdentityMatrix = MatrixUtils.createRealIdentityMatrix(oMarkovChain.getRowDimension() - 3);

        m_oBMatrix = ((new LUDecompositionImpl(oIdentityMatrix.subtract(oQMatrix)).getSolver().getInverse()).multiply(oRMatrix)).getData();
    }

    /**
     * Generate and then solve the Markov chain.
     */
    private void generateAndSolveMarkovChain() {
        if (m_oBMatrix == null) {

            generateMarkovChain();
            solveMarkovChain();
        }
    }

    /**
     * Get the reliability.
     *
     * @return the reliability.
     */
    public double getReliability() {
        generateAndSolveMarkovChain();

        return m_oBMatrix[0][0];
    }

    /**
     * Get the sfp.
     *
     * @return the sfp.
     */
    public double getSfp() {
        generateAndSolveMarkovChain();

        return m_oBMatrix[0][2];
    }

    /**
     * Get the pfp.
     *
     * @return the pfp.
     */
    public double getPfp() {
        generateAndSolveMarkovChain();

        return m_oBMatrix[0][1];
    }

    /**
     * Get the sep.
     *
     * @return the sep.
     */
    public double getSep() {
        generateAndSolveMarkovChain();

        return m_oBMatrix[1][2];
    }

    /**
     * Get the nmep.
     *
     * @return the nmep.
     */
    public double getNmep() {
        generateAndSolveMarkovChain();

        return m_oBMatrix[1][1];
    }

    /**
     * The the fp.
     *
     * @return the fp.
     */
    public double getFp() {
        return getSfp() + getPfp();
    }

    /**
     * Get the Markov chain.
     * @return the Markov chain.
     */
    public double[][] getMarkovChain() {
        generateAndSolveMarkovChain();

        return m_oMarkovChain;
    }

    /**
     * Get the B matrix.
     * @return the B matrix.
     */
    public double[][] getBMatrix() {
        generateAndSolveMarkovChain();

        return m_oBMatrix;
    }
}
